package com.xrui.hbase;

import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
import org.apache.avro.Schema;
import org.apache.avro.util.Utf8;


/**
 * Content of a HBase cell.
 * <p>
 * DecodedCell is obtained through a CellDecoder.
 *
 * @param <T> Type of the data in the cell.
 */
public final class DecodedCell<T> {
    /**
     * Constant representing a Decoded cell without a Schema. This can be used for reader or writer
     * Schemas.
     */
    public static final Schema NO_SCHEMA = null;
    /**
     * Decoded cell content.
     */
    private final T mData;
    /**
     * Schema used to read the cell data, or null for a non-Avro values.
     */
    private Schema mReaderSchema = null;
    /**
     * Schema used to write the cell data, or null for a non-Avro values.
     */
    private Schema mWriterSchema = null;

    /**
     * Initializes a DecodedCell instance.
     *
     * @param writerSchema Avro schema used to encode the cell, or null for non-Avro values.
     * @param readerSchema Avro schema used to decode the cell, or null for non-Avro values.
     * @param data         Cell content.
     */
    public DecodedCell(Schema writerSchema, Schema readerSchema, T data) {
        mWriterSchema = writerSchema;
        mReaderSchema = readerSchema;
        mData = data;
    }

    /**
     * Initializes a DecodedCell instance.
     *
     * @param writerAndReaderSchema Avro schema used to encode and decode the cell, or null for
     *                              non-Avro values.
     * @param data                  Cell content.
     */
    public DecodedCell(Schema writerAndReaderSchema, T data) {
        mWriterSchema = writerAndReaderSchema;
        mReaderSchema = writerAndReaderSchema;
        mData = data;
    }

    /**
     * Initializes a DecodedCell instance.
     *
     * @param data Cell content.
     */
    public DecodedCell(T data) {
        mData = data;
    }

    /**
     * @return the Avro schema used to decode the cell content, or null for non-Avro values.
     */
    public Schema getReaderSchema() {
        return mReaderSchema;
    }

    /**
     * @return the Avro schema used to encode the cell content, or null for non-Avro values.
     */
    public Schema getWriterSchema() {
        return mWriterSchema;
    }

    /**
     * @return the decoded cell content.
     */
    public T getData() {
        return mData;
    }

    /**
     * Determines whether the data contained in this DecodedCell is equivalent to another.  The
     * data is equivalent if they have the same schema and the same data, regardless of location.
     *
     * @param obj The object to compare.
     * @return Whether this contains the same data as the other DecodedCell.
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof DecodedCell<?>)) {
            return false;
        }
        final DecodedCell<?> other = (DecodedCell<?>) obj;
        if (!Objects.equal(this.getWriterSchema(), other.getWriterSchema())) {
            return false;
        }
        if (!Objects.equal(this.getReaderSchema(), other.getReaderSchema())) {
            return false;
        }

        Object data = getData();
        Object otherData = other.getData();
        // UTF8 strings don't compare well with other CharSequences:
        if ((data instanceof Utf8) ^ (otherData instanceof Utf8)) {
            data = data.toString();
            otherData = otherData.toString();
        }
        return data.equals(otherData);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(getWriterSchema(), getData());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        final ToStringHelper helper = Objects.toStringHelper(DecodedCell.class);
        if (mWriterSchema == null) {
            helper.add("data", mData);
        } else {
            helper
                .add("avro", mData)
                .add("writerSchema", mWriterSchema);
        }
        return helper.toString();
    }
}
